import { BoxGeometry3D, Color, MathUtils3D, MeshLambertMaterial3D, Node3D, Raycaster3D, Scene3D, StatsComponent, Vector2, WebGLRenderer3D } from 'feng3d-three';
import { CinematicCamera3D } from '../../extension/cameras/CinematicCamera3D';

import { $set } from '@feng3d/serialization';
import GUI from 'lil-gui';
import '../../extension/cameras/CinematicCamera3D';

let camera: CinematicCamera3D;
let scene: Scene3D;
let raycaster: Raycaster3D;
let renderer: WebGLRenderer3D;
let stats: StatsComponent;

const mouse = new Vector2();
let INTERSECTED: any;
const radius = 100;
let theta = 0;

init();
animate();

function init()
{
    camera = new Node3D().addComponent('CinematicCamera3D', { fov: 60, aspect: window.innerWidth / window.innerHeight, near: 1, far: 1000 });
    camera.setLens(5);
    camera.entity.position.set(2, 1, 500);

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xf0f0f0);

    scene.addComponent('AmbientLight3D', { color: { value: 0xffffff }, intensity: 0.3 });

    const light = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffffff }, intensity: 0.35 });
    light.entity.position.set(1, 1, 1).normalize();
    scene.entity.addChild(light.entity);

    const geometry = $set(new BoxGeometry3D(), { width: 20, height: 20, depth: 20 });

    for (let i = 0; i < 1500; i++)
    {
        const object = new Node3D().addComponent('Mesh3D', {
            geometry,
            material: $set(new MeshLambertMaterial3D(), { color: { value: Math.random() * 0xffffff } })
        });

        object.entity.position.x = Math.random() * 800 - 400;
        object.entity.position.y = Math.random() * 800 - 400;
        object.entity.position.z = Math.random() * 800 - 400;

        scene.entity.addChild(object.entity);
    }

    raycaster = new Raycaster3D();

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    document.body.appendChild(stats.dom);

    document.addEventListener('mousemove', onDocumentMouseMove);

    window.addEventListener('resize', onWindowResize);

    const effectController = {

        focalLength: 15,
        // jsDepthCalculation: true,
        // shaderFocus: false,
        //
        fstop: 2.8,
        // maxblur: 1.0,
        //
        showFocus: false,
        focalDepth: 3,
        // manualdof: false,
        // vignetting: false,
        // depthblur: false,
        //
        // threshold: 0.5,
        // gain: 2.0,
        // bias: 0.5,
        // fringe: 0.7,
        //
        // focalLength: 35,
        // noise: true,
        // pentagon: false,
        //
        // dithering: 0.0001

    };

    // eslint-disable-next-line func-style
    const matChanger = function ()
    {
        for (const e in effectController)
        {
            if (e in camera.postprocessing.bokeh_uniforms)
            {
                camera.postprocessing.bokeh_uniforms[e].value = effectController[e];
            }
        }

        camera.postprocessing.bokeh_uniforms['znear'].value = camera.near;
        camera.postprocessing.bokeh_uniforms['zfar'].value = camera.far;
        camera.setLens(effectController.focalLength, camera.frameHeight, effectController.fstop, camera.coc);
        effectController['focalDepth'] = camera.postprocessing.bokeh_uniforms['focalDepth'].value;
    };

    //

    const gui = new GUI();

    gui.add(effectController, 'focalLength', 1, 135, 0.01).onChange(matChanger);
    gui.add(effectController, 'fstop', 1.8, 22, 0.01).onChange(matChanger);
    gui.add(effectController, 'focalDepth', 0.1, 100, 0.001).onChange(matChanger);
    gui.add(effectController, 'showFocus', true as any).onChange(matChanger);

    matChanger();

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function onDocumentMouseMove(event: MouseEvent)
{
    event.preventDefault();

    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    theta += 0.1;

    camera.entity.position.x = radius * Math.sin(MathUtils3D.degToRad(theta));
    camera.entity.position.y = radius * Math.sin(MathUtils3D.degToRad(theta));
    camera.entity.position.z = radius * Math.cos(MathUtils3D.degToRad(theta));
    camera.entity.lookAt(scene.entity.position);

    camera.updateMatrixWorld();

    // find intersections

    raycaster.setFromCamera(mouse, camera);

    const intersects = raycaster.intersectObjects(scene.entity.children, false);

    if (intersects.length > 0)
    {
        const targetDistance = intersects[0].distance;

        camera.focusAt(targetDistance); // using Cinematic camera focusAt method

        if (INTERSECTED !== intersects[0].object)
        {
            if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);

            INTERSECTED = intersects[0].object;
            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
            INTERSECTED.material.emissive.setHex(0xff0000);
        }
    }
    else
    {
        if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);

        INTERSECTED = null;
    }

    //

    if (camera.postprocessing.enabled)
    {
        camera.renderCinematic(scene, renderer);
    }
    else
    {
        scene.overrideMaterial = null;

        renderer.clear();
        renderer.render(scene, camera);
    }
}
